#include "StdAfx.h"

#if defined(OPEN_AUTOMATE)

#include "OATester.h"
#include <StringUtils.h>


static const char OA_TESTER_CONFIG[] = "OpenAutomate.cfg";
static const char OA_BENCHMARK_FOLDER[] = "OpenAutomate/";

static const char CRY_OA_OPTION_RESOLUTION[] = "Screen Resolution";

static const char CRY_OA_OPTION_SYSSPEC[] = "Quality";
static const char CRY_OA_OPTION_SYSSPEC_HIGH[] = "High";
static const char CRY_OA_OPTION_SYSSPEC_VERYHIGH[] = "Very High";
static const char CRY_OA_OPTION_SYSSPEC_EXTREME[] = "Extreme";
static const char CRY_OA_OPTION_SYSSPEC_ULTRA[] = "Ultra";

static const char CRY_OA_OPTION_FULLSCREEN[] = "Fullscreen";

static const char CRY_OA_OPTION_DRIVER[] = "DirectX";
static const char CRY_OA_OPTION_DRIVER_DX11[] = "DX11";
static const char CRY_OA_OPTION_DRIVER_DX9[] = "DX9";


void OATester::OnSystemEvent(ESystemEvent event, UINT_PTR wparam, UINT_PTR lparam)
{
	switch (event)
	{
	case ESYSTEM_EVENT_LEVEL_PRECACHE_END:
		StartBenchmark();
		break;
	case ESYSTEM_EVENT_LEVEL_UNLOAD:
		EndBenchmark();
		break;
	default:
		break;
	}
}


OATester::OATester(const char* arg)
: m_curTestState(FIRST_RUN)
, m_options()
{
	oaVersion version;
	if (!oaInit(arg, &version))
		CryLogAlways("OA didn't initialize properly!");

	InitOptions();
}


OATester::~OATester()
{
	for (size_t i=0, size = m_options.size(); i<size; ++i)
	{
		if (m_options[i].DataType == OA_TYPE_ENUM)
		{
			delete [] m_options[i].Value.Enum;
		}
	}
}


void OATester::InitOptions()
{
	AddOptionEnum(CRY_OA_OPTION_SYSSPEC, CRY_OA_OPTION_SYSSPEC_HIGH);
	AddOptionEnum(CRY_OA_OPTION_SYSSPEC, CRY_OA_OPTION_SYSSPEC_VERYHIGH);
	AddOptionEnum(CRY_OA_OPTION_SYSSPEC, CRY_OA_OPTION_SYSSPEC_EXTREME);
	AddOptionEnum(CRY_OA_OPTION_SYSSPEC, CRY_OA_OPTION_SYSSPEC_ULTRA);

	AddOptionBool(CRY_OA_OPTION_FULLSCREEN);

	AddOptionEnum(CRY_OA_OPTION_DRIVER, CRY_OA_OPTION_DRIVER_DX9);

	AddOptionEnum(CRY_OA_OPTION_DRIVER, CRY_OA_OPTION_DRIVER_DX11);

	const int numResolutions = gEnv->pRenderer->EnumDisplayFormats(0);
	if (numResolutions > 0)
	{
		SDispFormat* formats = new SDispFormat[numResolutions];
		gEnv->pRenderer->EnumDisplayFormats(formats);

		int prevHeight = 0, prevWidth = 0;
		for (int formatIndex = 0; formatIndex < numResolutions; ++formatIndex)
		{
			const int width = formats[formatIndex].m_Width;
			const int height = formats[formatIndex].m_Height;

			if (!(width == prevWidth && height == prevHeight))
			{
				char resolution[32];
				sprintf(resolution, "%dx%d", width, height);
				AddOptionEnum(CRY_OA_OPTION_RESOLUTION, resolution);

				prevHeight = height;
				prevWidth = width;
			}
		}

		delete [] formats;
	}
}


void OATester::AddOption( const char* name, oaOptionDataType type)
{
	oaNamedOption option;
	oaInitOption(&option);
	option.Name = name;
	option.DataType = type;
	m_options.push_back(option);
}


void OATester::AddOptionString(const char* name)
{
	AddOption(name, OA_TYPE_STRING);
}


void OATester::AddOptionBool(const char* name)
{
	AddOption(name, OA_TYPE_BOOL);
}


void OATester::AddOptionInt(const char* name, int min, int max, int numSteps)
{
	oaNamedOption option;
	oaInitOption(&option);
	option.Name = name;
	option.DataType = OA_TYPE_INT;
	option.NumSteps = numSteps;
	option.MinValue.Int = min;
	option.MaxValue.Int = max;
	m_options.push_back(option);
}


void OATester::AddOptionFloat(const char* name, float max, float min, int numSteps)
{
	oaNamedOption option;
	oaInitOption(&option);
	option.Name = name;
	option.DataType = OA_TYPE_FLOAT;
	option.NumSteps = numSteps;
	option.MinValue.Float = min;
	option.MaxValue.Float = max;
	m_options.push_back(option);
}


void OATester::AddOptionEnum(const char* name, const char* enumValue)
{
	oaNamedOption option;
	oaInitOption(&option);
	option.Name = name;
	option.DataType = OA_TYPE_ENUM;
	char* value = new char[strlen(enumValue)+1];
	strcpy(value, enumValue);
	option.Value.Enum = oaString(value);
	m_options.push_back(option);
}


void OATester::GetAllOptions()
{
	for (size_t i=0, size = m_options.size(); i<size; ++i)
	{
		oaAddOption(&m_options[i]);
	}
}


void OATester::GetCurrentOptions()
{
	{
		int width = gEnv->pConsole->GetCVar("r_width")->GetIVal();
		int height = gEnv->pConsole->GetCVar("r_height")->GetIVal();
		char openAutomateResolution[32] = "";

		sprintf_s(openAutomateResolution, "%dx%d", width, height);
		oaValue currentValue;
		currentValue.Enum = (oaChar*) openAutomateResolution;
		oaAddOptionValue(CRY_OA_OPTION_RESOLUTION, OA_TYPE_ENUM, &currentValue);
	}

	{
		int sys_spec = gEnv->pConsole->GetCVar("sys_spec")->GetIVal();
		oaValue currentValue;
		switch (sys_spec)
		{
		case 4:
			currentValue.Enum = (oaChar*) CRY_OA_OPTION_SYSSPEC_ULTRA;
			break;
		case 3:
			currentValue.Enum = (oaChar*) CRY_OA_OPTION_SYSSPEC_EXTREME;
			break;
		case 2:
			currentValue.Enum = (oaChar*) CRY_OA_OPTION_SYSSPEC_VERYHIGH;
			break;
		default:
			currentValue.Enum = (oaChar*) CRY_OA_OPTION_SYSSPEC_HIGH;
			break;
		}
		oaAddOptionValue(CRY_OA_OPTION_SYSSPEC, OA_TYPE_ENUM, &currentValue);
	}

	{
		const char* driver = gEnv->pConsole->GetCVar("r_driver")->GetString();
		oaValue currentValue;
		if (!stricmp(driver, "DX11"))
			currentValue.Enum = (oaChar*) CRY_OA_OPTION_DRIVER_DX11;
		else
			currentValue.Enum = (oaChar*) CRY_OA_OPTION_DRIVER_DX9;
		oaAddOptionValue(CRY_OA_OPTION_DRIVER, OA_TYPE_ENUM, &currentValue);
	}

	{
		int fullscreen = gEnv->pConsole->GetCVar("r_fullscreen")->GetIVal();
		oaValue currentValue;
		currentValue.Bool = (oaBool) (fullscreen != 0) ? OA_TRUE : OA_FALSE;
		oaAddOptionValue(CRY_OA_OPTION_FULLSCREEN, OA_TYPE_BOOL, &currentValue);
	}
}

void OATester::GetBenchmarks()
{
	string filePat = OA_BENCHMARK_FOLDER;
	filePat += "*.cfg";

	_finddata_t fd;
	intptr_t hfil = 0;
	if ((hfil = gEnv->pCryPak->FindFirst(filePat.c_str(), &fd)) == -1)
		return;

	do
	{
		if (!(fd.attrib & _A_SUBDIR))
		{
			CryStringUtils::StripFileExtension(fd.name);
			oaAddBenchmark(fd.name);
		}
	} while(!gEnv->pCryPak->FindNext(hfil, &fd));

	gEnv->pCryPak->FindClose(hfil);
}


void OATester::LoadBenchmark(const char* benchmarkName)
{
	CryLogAlways("[OATester] RunBenchmark \"%s\"", benchmarkName);

	string filePath = OA_BENCHMARK_FOLDER;
	filePath += benchmarkName;
	filePath += ".cfg";

	LoadBenchmarkConfiguration(filePath.c_str());
}


void OATester::LoadBenchmarkConfiguration(const char* cfg)
{
	CDebugAllowFileAccess allowFileAccess;
	FILE* f = gEnv->pCryPak->FOpen(cfg, "rb");
	allowFileAccess.End();

	if (f)
	{
		const long filesize = gEnv->pCryPak->FGetSize(f);
		if (filesize > 0)
		{
			char* buffer = new char[filesize+1];
			if (buffer)
			{
				gEnv->pCryPak->FReadRaw(buffer, 1, filesize, f);
				buffer[filesize] = 0;

				const char* strLast = &buffer[filesize];
				char* str = buffer;

				while (str < strLast)
				{
					char* s = str;
					while (str < strLast && *str != '\n' && *str != '\r')
						str++;
					*str = '\0';
					str++;
					while (str < strLast && (*str == '\n' || *str == '\r'))
						str++;

					if (s[0])
						gEnv->pConsole->ExecuteString(s);
				}

				delete [] buffer;
			}
		}
		gEnv->pCryPak->FClose(f);
	}
	else
	{
		CryLogAlways("[OATester] Cannot open configuration file \"%s\"!", cfg);
	}
}


static void SerializeCVar(FILE* f, const char* name)
{
	ICVar* pCVar = gEnv->pConsole->GetCVar(name);
	if (pCVar)
	{
		char buffer[1024];
		switch (pCVar->GetType())
		{
		case CVAR_INT:
			sprintf_s(buffer, "%s = %d\n", pCVar->GetName(), pCVar->GetIVal());
			break;
		case CVAR_FLOAT:
			sprintf_s(buffer, "%s = %.3f\n", pCVar->GetName(), pCVar->GetFVal());
			break;
		case CVAR_STRING:
			sprintf_s(buffer, "%s = %s\n", pCVar->GetName(), pCVar->GetString());
			break;
		}

		fputs(buffer, f);
	}
}


void OATester::SetOptions()
{
	oaNamedOption *option;
	while ((option = oaGetNextOption()) != 0)
	{
		oaNamedOption* found = 0;
		for (size_t i=0, size = m_options.size(); !found && i<size; ++i)
		{
			if (!strcmp(option->Name, m_options[i].Name))
			{
				found = &m_options[i];
			}
		}

		if (!found)
			continue;

		if (!strcmp(option->Name, CRY_OA_OPTION_RESOLUTION))
		{
			int width = 0, height = 0;
			sscanf_s(option->Value.Enum, "%dx%d",&width, &height);
			gEnv->pConsole->GetCVar("r_width")->Set(width);
			gEnv->pConsole->GetCVar("r_height")->Set(height);
			continue;
		}

		if (!strcmp(option->Name, CRY_OA_OPTION_SYSSPEC))
		{
			int spec = 1;

			if (!strcmp(option->Value.Enum, CRY_OA_OPTION_SYSSPEC_ULTRA))
				spec = 4;
			else if (!strcmp(option->Value.Enum, CRY_OA_OPTION_SYSSPEC_EXTREME))
				spec = 3;
			else if (!strcmp(option->Value.Enum, CRY_OA_OPTION_SYSSPEC_VERYHIGH))
				spec = 2;

			gEnv->pConsole->GetCVar("sys_spec")->Set(spec);
			continue;
		}

		if (!strcmp(option->Name, CRY_OA_OPTION_DRIVER))
		{
			const char* driver = "DX9";
			if (!strcmp(option->Value.Enum, CRY_OA_OPTION_DRIVER_DX11))
				driver = "DX11";

			gEnv->pConsole->GetCVar("r_driver")->Set(driver);
			continue;
		}

		if (!strcmp(option->Name, CRY_OA_OPTION_FULLSCREEN))
		{
			gEnv->pConsole->GetCVar("r_fullscreen")->Set(option->Value.Bool == OA_TRUE ? 1 : 0);
			continue;
		}
	}

	// serialize
	{
		FILE* f = fxopen(OA_TESTER_CONFIG, "w");
		if (f)
		{
			SerializeCVar(f, "sys_spec");
			SerializeCVar(f, "r_driver");
			SerializeCVar(f, "r_width");
			SerializeCVar(f, "r_height");
			SerializeCVar(f, "r_fullscreen");
			fclose(f);
		}
	}
}


oaCommand OATester::GetNextCommand()
{
	oaCommand cmd;
	oaInitCommand(&cmd);
	oaGetNextCommand(&cmd);
	return cmd;
}


void OATester::ReadOaCommands()
{
	while (true)
	{
		oaCommand cmd = GetNextCommand();
		switch (cmd.Type)
		{
		case OA_CMD_RUN:
			m_curTestState = RUN;
			return;
		case OA_CMD_EXIT:
			if (m_curTestState == RUN_BENCHMARK)
				oaEndBenchmark();
			m_curTestState = EXIT_APPLICATION;
			return;
		case OA_CMD_GET_ALL_OPTIONS:
			GetAllOptions();
			break;
		case OA_CMD_GET_CURRENT_OPTIONS:
			GetCurrentOptions();
			break;
		case OA_CMD_SET_OPTIONS:
			SetOptions();
			break;
		case OA_CMD_GET_BENCHMARKS:
			GetBenchmarks();
			break;
		case OA_CMD_RUN_BENCHMARK:
			LoadBenchmark(cmd.BenchmarkName);
			m_curTestState = RUN_BENCHMARK;
			return;
		}
	}
}


void OATester::StartBenchmark()
{
	if (m_curTestState != END_BENCHMARK)
		oaStartBenchmark();
}


void OATester::EndBenchmark()
{
	m_curTestState = END_BENCHMARK;
	oaEndBenchmark();
}


void OATester::DisplayFrame(float appTime)
{
	oaDisplayFrame(appTime);
}


bool OATester::ShouldReloadCommands()
{
	return m_curTestState == END_BENCHMARK || m_curTestState == FIRST_RUN;
}


void OATester::PreUpdate()
{
	if (m_curTestState == EXIT_APPLICATION)
		gEnv->pSystem->Quit();

	if (ShouldReloadCommands())
		ReadOaCommands();
}


void OATester::PostUpdate()
{
	if (m_curTestState == RUN_BENCHMARK)
		DisplayFrame(gEnv->pTimer->GetCurrTime());
}


#endif //#if defined(OPEN_AUTOMATE)
